package com.jingdianjichi.subject.common.util;

/**
 * Twitter的Snowflake算法实现，用于生成全局唯一ID。
 * Snowflake ID由64位组成：1位未使用，41位时间戳，10位数据机器标识（5位datacenterId，5位workerId），12位序列号。
 */
public class IdWorkerUtil {

    private long workerId; // 工作机器ID
    private long datacenterId; // 数据中心ID
    private long sequence; // 序列号
    private long twepoch = 1585644268888L; // 基准时间戳

    private long workerIdBits = 5L; // workerId所占的位数
    private long datacenterIdBits = 5L; // datacenterId所占的位数
    private long sequenceBits = 12L; // 序列号占用的位数

    private long maxWorkerId = -1L ^ (-1L << workerIdBits); // workerId最大值
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // datacenterId最大值

    private long workerIdShift = sequenceBits; // workerId左移位数
    private long datacenterIdShift = sequenceBits + workerIdBits; // datacenterId左移位数
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数
    private long sequenceMask = -1L ^ (-1L << sequenceBits); // 生成序列的掩码

    private long lastTimestamp = -1L; // 上一次生成ID的时间戳

    // 构造函数
    public IdWorkerUtil(long workerId, long datacenterId, long sequence) {
        // 检查workerId和datacenterId是否超出最大值
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    // 生成ID的核心方法
    public synchronized long nextId() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过，这时抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                            lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变，毫秒内序列重置
            sequence = 0;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;

        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) | sequence;
    }

    // 阻塞到下一个毫秒，直到获得新的时间戳
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    // 获取当前时间戳
    private long timeGen() {
        return System.currentTimeMillis();
    }

}
